#region Copyright & License

//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#endregion

using System;
using System.IO;
using log4net.Core;
using log4net.Util;

namespace log4net.Appender {
  /// <summary>
  /// Send an email when a specific logging event occurs, typically on errors 
  /// or fatal errors. Rather than sending via smtp it writes a file into the
  /// directory specified by <see cref="PickupDir"/>. This allows services such
  /// as the IIS SMTP agent to manage sending the messages.
  /// </summary>
  /// <remarks>
  /// <para>
  /// The configuration for this appender is identical to that of the <c>SMTPAppender</c>,
  /// except that instead of specifying the <c>SMTPAppender.SMTPHost</c> you specify
  /// <see cref="PickupDir"/>.
  /// </para>
  /// <para>
  /// The number of logging events delivered in this e-mail depend on
  /// the value of <see cref="BufferingAppenderSkeleton.BufferSize"/> option. The
  /// <see cref="SmtpPickupDirAppender"/> keeps only the last
  /// <see cref="BufferingAppenderSkeleton.BufferSize"/> logging events in its 
  /// cyclic buffer. This keeps memory requirements at a reasonable level while 
  /// still delivering useful application context.
  /// </para>
  /// </remarks>
  /// <author>Niall Daley</author>
  /// <author>Nicko Cadell</author>
  public class SmtpPickupDirAppender : BufferingAppenderSkeleton {
    #region Public Instance Constructors

    #endregion Public Instance Constructors

    #region Public Instance Properties

    /// <summary>
    /// Gets or sets a semicolon-delimited list of recipient e-mail addresses.
    /// </summary>
    /// <value>
    /// A semicolon-delimited list of e-mail addresses.
    /// </value>
    /// <remarks>
    /// <para>
    /// A semicolon-delimited list of e-mail addresses.
    /// </para>
    /// </remarks>
    public string To {
      get { return m_to; }
      set { m_to = value; }
    }

    /// <summary>
    /// Gets or sets the e-mail address of the sender.
    /// </summary>
    /// <value>
    /// The e-mail address of the sender.
    /// </value>
    /// <remarks>
    /// <para>
    /// The e-mail address of the sender.
    /// </para>
    /// </remarks>
    public string From {
      get { return m_from; }
      set { m_from = value; }
    }

    /// <summary>
    /// Gets or sets the subject line of the e-mail message.
    /// </summary>
    /// <value>
    /// The subject line of the e-mail message.
    /// </value>
    /// <remarks>
    /// <para>
    /// The subject line of the e-mail message.
    /// </para>
    /// </remarks>
    public string Subject {
      get { return m_subject; }
      set { m_subject = value; }
    }

    /// <summary>
    /// Gets or sets the path to write the messages to.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Gets or sets the path to write the messages to. This should be the same
    /// as that used by the agent sending the messages.
    /// </para>
    /// </remarks>
    public string PickupDir {
      get { return m_pickupDir; }
      set { m_pickupDir = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="SecurityContext"/> used to write to the pickup directory.
    /// </summary>
    /// <value>
    /// The <see cref="SecurityContext"/> used to write to the pickup directory.
    /// </value>
    /// <remarks>
    /// <para>
    /// Unless a <see cref="SecurityContext"/> specified here for this appender
    /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
    /// security context to use. The default behavior is to use the security context
    /// of the current thread.
    /// </para>
    /// </remarks>
    public SecurityContext SecurityContext {
      get { return m_securityContext; }
      set { m_securityContext = value; }
    }

    #endregion Public Instance Properties

    #region Override implementation of BufferingAppenderSkeleton

    /// <summary>
    /// Sends the contents of the cyclic buffer as an e-mail message.
    /// </summary>
    /// <param name="events">The logging events to send.</param>
    /// <remarks>
    /// <para>
    /// Sends the contents of the cyclic buffer as an e-mail message.
    /// </para>
    /// </remarks>
    protected override void SendBuffer(LoggingEvent[] events) {
      // Note: this code already owns the monitor for this
      // appender. This frees us from needing to synchronize again.
      try {
        string filePath = null;
        StreamWriter writer = null;

        // Impersonate to open the file
        using (SecurityContext.Impersonate(this)) {
          filePath = Path.Combine(m_pickupDir, SystemInfo.NewGuid().ToString("N"));
          writer = File.CreateText(filePath);
        }

        if (writer == null)
          ErrorHandler.Error("Failed to create output file for writing [" + filePath + "]", null,
                             ErrorCode.FileOpenFailure);
        else
          using (writer) {
            writer.WriteLine("To: " + m_to);
            writer.WriteLine("From: " + m_from);
            writer.WriteLine("Subject: " + m_subject);
            writer.WriteLine("");

            string t = Layout.Header;
            if (t != null)
              writer.Write(t);

            for (int i = 0; i < events.Length; i++) // Render the event and append the text to the buffer
              RenderLoggingEvent(writer, events[i]);

            t = Layout.Footer;
            if (t != null)
              writer.Write(t);

            writer.WriteLine("");
            writer.WriteLine(".");
          }
      } catch (Exception e) {
        ErrorHandler.Error("Error occurred while sending e-mail notification.", e);
      }
    }

    #endregion Override implementation of BufferingAppenderSkeleton

    #region Override implementation of AppenderSkeleton

    /// <summary>
    /// This appender requires a <see cref="Layout"/> to be set.
    /// </summary>
    /// <value><c>true</c></value>
    /// <remarks>
    /// <para>
    /// This appender requires a <see cref="Layout"/> to be set.
    /// </para>
    /// </remarks>
    protected override bool RequiresLayout {
      get { return true; }
    }

    /// <summary>
    /// Activate the options on this appender. 
    /// </summary>
    /// <remarks>
    /// <para>
    /// This is part of the <see cref="IOptionHandler"/> delayed object
    /// activation scheme. The <see cref="ActivateOptions"/> method must 
    /// be called on this object after the configuration properties have
    /// been set. Until <see cref="ActivateOptions"/> is called this
    /// object is in an undefined state and must not be used. 
    /// </para>
    /// <para>
    /// If any of the configuration properties are modified then 
    /// <see cref="ActivateOptions"/> must be called again.
    /// </para>
    /// </remarks>
    public override void ActivateOptions() {
      base.ActivateOptions();

      if (m_securityContext == null)
        m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);

      using (SecurityContext.Impersonate(this))
        m_pickupDir = ConvertToFullPath(m_pickupDir.Trim());
    }

    #endregion Override implementation of AppenderSkeleton

    #region Protected Static Methods

    /// <summary>
    /// Convert a path into a fully qualified path.
    /// </summary>
    /// <param name="path">The path to convert.</param>
    /// <returns>The fully qualified path.</returns>
    /// <remarks>
    /// <para>
    /// Converts the path specified to a fully
    /// qualified path. If the path is relative it is
    /// taken as relative from the application base 
    /// directory.
    /// </para>
    /// </remarks>
    protected static string ConvertToFullPath(string path) {
      return SystemInfo.ConvertToFullPath(path);
    }

    #endregion Protected Static Methods

    #region Private Instance Fields

    string m_from;
    string m_pickupDir;

    /// <summary>
    /// The security context to use for privileged calls
    /// </summary>
    SecurityContext m_securityContext;

    string m_subject;
    string m_to;

    #endregion Private Instance Fields
  }
}